/*
Copyright 2022 The Karmada Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package luavm

import (
	"encoding/json"
	"fmt"
	"reflect"
	"testing"

	lua "github.com/yuin/gopher-lua"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/utils/ptr"

	configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
	workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
	"github.com/karmada-io/karmada/pkg/util/helper"
)

func TestGetReplicas(t *testing.T) {
	vm := New(false, 1)
	tests := []struct {
		name         string
		deploy       *appsv1.Deployment
		luaScript    string
		expected     bool
		wantReplica  int32
		wantRequires *workv1alpha2.ReplicaRequirements
	}{
		{
			name: "Test GetReplica with kube.accuratePodRequirements",
			deploy: &appsv1.Deployment{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Deployment",
					APIVersion: "v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Namespace: "foo",
					Name:      "bar",
				},
				Spec: appsv1.DeploymentSpec{
					Replicas: ptr.To[int32](1),
					Template: corev1.PodTemplateSpec{
						Spec: corev1.PodSpec{
							NodeSelector: map[string]string{"foo": "foo1"},
							Tolerations:  []corev1.Toleration{{Key: "bar", Operator: corev1.TolerationOpExists}},
							Containers: []corev1.Container{
								{Resources: corev1.ResourceRequirements{Limits: map[corev1.ResourceName]resource.Quantity{
									corev1.ResourceCPU:    resource.MustParse("100m"),
									corev1.ResourceMemory: resource.MustParse("100M"),
								}}},
								{Resources: corev1.ResourceRequirements{Limits: map[corev1.ResourceName]resource.Quantity{
									corev1.ResourceCPU:    resource.MustParse("1.2"),
									corev1.ResourceMemory: resource.MustParse("1.2G"),
								}}},
							},
						},
					},
				},
			},
			expected: true,
			luaScript: `
local kube = require("kube")
function GetReplicas(desiredObj)
	replica = desiredObj.spec.replicas
	requires = kube.accuratePodRequirements(desiredObj.spec.template)
	return replica, requires 
end`,
			wantReplica: 1,
			wantRequires: &workv1alpha2.ReplicaRequirements{
				NodeClaim: &workv1alpha2.NodeClaim{
					NodeSelector: map[string]string{"foo": "foo1"},
					Tolerations:  []corev1.Toleration{{Key: "bar", Operator: corev1.TolerationOpExists}},
				},
				ResourceRequest: map[corev1.ResourceName]resource.Quantity{
					corev1.ResourceCPU:    resource.MustParse("1.3"),
					corev1.ResourceMemory: resource.MustParse("1.3G"),
				},
			},
		},
		{
			name: "Test GetReplica with kube.resourceAdd",
			deploy: &appsv1.Deployment{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Deployment",
					APIVersion: "v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Namespace: "foo",
					Name:      "bar",
				},
				Spec: appsv1.DeploymentSpec{
					Replicas: ptr.To[int32](1),
					Template: corev1.PodTemplateSpec{
						Spec: corev1.PodSpec{
							NodeSelector: map[string]string{"foo": "foo1"},
							Tolerations:  []corev1.Toleration{{Key: "bar", Operator: corev1.TolerationOpExists}},
							Containers: []corev1.Container{
								{Resources: corev1.ResourceRequirements{Limits: map[corev1.ResourceName]resource.Quantity{
									corev1.ResourceCPU:    resource.MustParse("100m"),
									corev1.ResourceMemory: resource.MustParse("100M"),
								}}},
								{Resources: corev1.ResourceRequirements{Limits: map[corev1.ResourceName]resource.Quantity{
									corev1.ResourceCPU:    resource.MustParse("1.2"),
									corev1.ResourceMemory: resource.MustParse("1.2G"),
								}}},
							},
						},
					},
				},
			},
			expected: true,
			luaScript: `
local kube = require("kube")
function GetReplicas(desiredObj)
	replica = desiredObj.spec.replicas
	requires = {
		nodeClaim = {
			nodeSelector = desiredObj.spec.template.spec.nodeSelector,
			tolerations = desiredObj.spec.template.spec.tolerations
    	},
		resourceRequest = {},
	}
	for i = 1, #desiredObj.spec.template.spec.containers do
		requires.resourceRequest.cpu = kube.resourceAdd(requires.resourceRequest.cpu, desiredObj.spec.template.spec.containers[i].resources.limits.cpu)
		requires.resourceRequest.memory = kube.resourceAdd(requires.resourceRequest.memory, desiredObj.spec.template.spec.containers[i].resources.limits.memory)
	end
	return replica, requires 
end`,
			wantReplica: 1,
			wantRequires: &workv1alpha2.ReplicaRequirements{
				NodeClaim: &workv1alpha2.NodeClaim{
					NodeSelector: map[string]string{"foo": "foo1"},
					Tolerations:  []corev1.Toleration{{Key: "bar", Operator: corev1.TolerationOpExists}},
				},
				ResourceRequest: map[corev1.ResourceName]resource.Quantity{
					corev1.ResourceCPU:    resource.MustParse("1.3"),
					corev1.ResourceMemory: resource.MustParse("1.3G"),
				},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			toUnstructured, _ := helper.ToUnstructured(tt.deploy)
			replicas, requires, err := vm.GetReplicas(toUnstructured, tt.luaScript)
			if err != nil {
				t.Fatal(err.Error())
			}
			if !reflect.DeepEqual(replicas, tt.wantReplica) {
				t.Errorf("GetReplicas() got = %v, want %v", replicas, tt.wantReplica)
			}

			if got, want := requires.ResourceRequest.Cpu(), tt.wantRequires.ResourceRequest.Cpu(); !got.Equal(*want) {
				t.Errorf("GetReplicas() got Cpu = %s, want %s", got, want)
			}
			if got, want := requires.ResourceRequest.Memory(), tt.wantRequires.ResourceRequest.Memory(); !got.Equal(*want) {
				t.Errorf("GetReplicas() got Memory = %s, want %s", got, want)
			}

			requires.ResourceRequest, tt.wantRequires.ResourceRequest = nil, nil
			if !reflect.DeepEqual(requires, tt.wantRequires) {
				t.Errorf("GetReplicas() got = %v, want %v", requires, tt.wantRequires)
			}
		})
	}
}

// MockMultiPodTemplateWorkload simulates a CRD with multiple pod templates,
// mirroring the structure of a real-world Kubernetes object.
type MockMultiPodTemplateWorkload struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	Spec              MockWorkloadSpec `json:"spec,omitempty"`
}

// MockWorkloadSpec contains the different components, each with its own PodSpec.
type MockWorkloadSpec struct {
	Master MockComponentSpec `json:"master"`
	Worker MockComponentSpec `json:"worker"`
}

// MockComponentSpec now includes Replicas and directly embeds a PodSpec
// to perfectly simulate a pod template.
type MockComponentSpec struct {
	Replicas int32          `json:"replicas"`
	Spec     corev1.PodSpec `json:"spec"`
}

// DeepCopyObject implements the runtime.Object interface.
func (in *MockMultiPodTemplateWorkload) DeepCopyObject() runtime.Object {
	out := new(MockMultiPodTemplateWorkload)
	bytes, err := json.Marshal(in)
	if err != nil {
		panic(fmt.Sprintf("Failed to marshal mock object in DeepCopy: %v", err))
	}
	if err := json.Unmarshal(bytes, out); err != nil {
		panic(fmt.Sprintf("Failed to unmarshal mock object in DeepCopy: %v", err))
	}
	return out
}

func TestGetComponents(t *testing.T) {
	vm := New(true, 1)

	mockWorkload := &MockMultiPodTemplateWorkload{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "testing.karmada.io/v1alpha1",
			Kind:       "MockMultiPodTemplateWorkload",
		},
		ObjectMeta: metav1.ObjectMeta{
			Namespace: "default",
			Name:      "my-mock-app",
		},
		Spec: MockWorkloadSpec{
			Master: MockComponentSpec{
				Replicas: 1,
				Spec: corev1.PodSpec{
					NodeSelector: map[string]string{"foo": "foo1"},
					Tolerations:  []corev1.Toleration{{Key: "bar", Operator: corev1.TolerationOpExists}},
					Containers: []corev1.Container{
						{
							Name: "master",
							Resources: corev1.ResourceRequirements{
								Requests: corev1.ResourceList{
									corev1.ResourceCPU:    resource.MustParse("1"),
									corev1.ResourceMemory: resource.MustParse("1Gi"),
								},
							},
						},
					},
				},
			},
			Worker: MockComponentSpec{
				Replicas: 3,
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Name: "worker",
							Resources: corev1.ResourceRequirements{
								Requests: corev1.ResourceList{
									corev1.ResourceCPU:    resource.MustParse("500m"),
									corev1.ResourceMemory: resource.MustParse("2Gi"),
								},
							},
						},
					},
				},
			},
		},
	}
	mockWorkload.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{
		Group:   "testing.karmada.io",
		Version: "v1alpha1",
		Kind:    "MockMultiPodTemplateWorkload",
	})

	tests := []struct {
		name           string
		object         runtime.Object
		luaScript      string
		wantErr        bool
		wantComponents []workv1alpha2.Component
	}{
		{
			name:   "Successfully parse multiple PodSpec templates from a mock workload",
			object: mockWorkload,
			// This Lua script now navigates a real PodSpec and uses the 'kube' helper library.
			luaScript: `
function GetComponents(obj)
    local kube = require("kube")
    local components = {}

    -- Parse the Master component's PodSpec
    table.insert(components, {
        name = "master",
        replicas = obj.spec.master.replicas,
        replicaRequirements = kube.accuratePodRequirements(obj.spec.master)
    })

    -- Parse the Worker component's PodSpec
    table.insert(components, {
        name = "worker",
        replicas = obj.spec.worker.replicas,
        replicaRequirements = kube.accuratePodRequirements(obj.spec.worker)
    })

    return components
end
`,
			wantErr: false,
			wantComponents: []workv1alpha2.Component{
				{
					Name:     "master",
					Replicas: 1,
					ReplicaRequirements: &workv1alpha2.ComponentReplicaRequirements{
						NodeClaim: &workv1alpha2.NodeClaim{
							NodeSelector: map[string]string{"foo": "foo1"},
							Tolerations:  []corev1.Toleration{{Key: "bar", Operator: corev1.TolerationOpExists}},
						},
						ResourceRequest: map[corev1.ResourceName]resource.Quantity{
							corev1.ResourceCPU:    resource.MustParse("1"),
							corev1.ResourceMemory: resource.MustParse("1Gi"),
						},
					},
				},
				{
					Name:     "worker",
					Replicas: 3,
					ReplicaRequirements: &workv1alpha2.ComponentReplicaRequirements{
						ResourceRequest: map[corev1.ResourceName]resource.Quantity{
							corev1.ResourceCPU:    resource.MustParse("500m"),
							corev1.ResourceMemory: resource.MustParse("2Gi"),
						},
					},
				},
			},
		},
		{
			name:   "Script successfully returns nil for no components",
			object: mockWorkload,
			luaScript: `
function GetComponents(obj)
    return nil
end
`,
			wantErr:        false,
			wantComponents: nil,
		},
		{
			name:   "Script returns an invalid type (string) and should error",
			object: mockWorkload,
			luaScript: `
function GetComponents(obj)
    return "this is not a table"
end
`,
			wantErr:        true,
			wantComponents: nil,
		},
		{
			name:   "Script has a syntax error and should error",
			object: mockWorkload,
			luaScript: `
function GetComponents(obj)
    local x = {
    return x -- syntax error: unbalanced brackets
end
`,
			wantErr:        true,
			wantComponents: nil,
		},
		{
			name:   "Script is missing the GetComponents function and should error",
			object: mockWorkload,
			luaScript: `
-- This script is valid but does not define the required function.
function GetReplicas(obj)
    return 1
end
`,
			wantErr:        true,
			wantComponents: nil,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			objUnstructured, err := helper.ToUnstructured(tt.object)
			if err != nil {
				t.Fatalf("Failed to convert object to unstructured: %v", err)
			}

			components, err := vm.GetComponents(objUnstructured, tt.luaScript)

			if (err != nil) != tt.wantErr {
				t.Fatalf("GetComponents() error = %v, wantErr %v", err, tt.wantErr)
			}

			if !tt.wantErr && !reflect.DeepEqual(components, tt.wantComponents) {
				t.Errorf("GetComponents() = \n%#v, \nwant \n%#v", components, tt.wantComponents)
			}
		})
	}
}

func TestReviseDeploymentReplica(t *testing.T) {
	tests := []struct {
		name        string
		object      *unstructured.Unstructured
		replica     int32
		expected    *unstructured.Unstructured
		expectError bool
		luaScript   string
	}{
		{
			name: "Test ReviseDeploymentReplica",
			object: &unstructured.Unstructured{
				Object: map[string]interface{}{
					"apiVersion": "apps/v1",
					"kind":       "Deployment",
					"metadata": map[string]interface{}{
						"name": "fake-deployment",
					},
					"spec": map[string]interface{}{
						"replicas": 1,
					},
				},
			},
			replica:     3,
			expectError: true,
			luaScript: `function ReviseReplica(desiredObj, desiredReplica)
							desiredObj.spec.replicas = desiredReplica  
							return desiredObj
                           end`,
		},
		{
			name: "revise deployment replica",
			object: &unstructured.Unstructured{
				Object: map[string]interface{}{
					"apiVersion": "apps/v1",
					"kind":       "Deployment",
					"metadata": map[string]interface{}{
						"name": "fake-deployment",
					},
					"spec": map[string]interface{}{
						"replicas": int64(1),
					},
				},
			},
			replica: 3,
			expected: &unstructured.Unstructured{
				Object: map[string]interface{}{
					"apiVersion": "apps/v1",
					"kind":       "Deployment",
					"metadata": map[string]interface{}{
						"name": "fake-deployment",
					},
					"spec": map[string]interface{}{
						"replicas": int64(2),
					},
				},
			},
			expectError: false,
			luaScript: `function ReviseReplica(desiredObj, desiredReplica)  
							desiredObj.spec.replicas = desiredReplica	
							return desiredObj 
						end`,
		},
	}
	vm := New(false, 1)

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			res, err := vm.ReviseReplica(tt.object, int64(tt.replica), tt.luaScript)
			if err != nil {
				t.Error(err.Error())
			}
			deploy := &appsv1.Deployment{}
			err = runtime.DefaultUnstructuredConverter.FromUnstructured(res.UnstructuredContent(), deploy)
			if err == nil && *deploy.Spec.Replicas == tt.replica {
				t.Log("Success Test")
			}
			if err != nil {
				t.Error(err.Error())
			}
		})
	}
}

func TestAggregateDeploymentStatus(t *testing.T) {
	statusMap := map[string]interface{}{
		"replicas":            0,
		"readyReplicas":       1,
		"updatedReplicas":     0,
		"availableReplicas":   0,
		"unavailableReplicas": 0,
	}
	raw, _ := helper.BuildStatusRawExtension(statusMap)
	aggregatedStatusItems := []workv1alpha2.AggregatedStatusItem{
		{ClusterName: "member1", Status: raw, Applied: true},
		{ClusterName: "member2", Status: raw, Applied: true},
	}

	oldDeploy := &appsv1.Deployment{
		TypeMeta: metav1.TypeMeta{
			Kind:       "Deployment",
			APIVersion: appsv1.SchemeGroupVersion.String(),
		},
		Status: appsv1.DeploymentStatus{Replicas: 0, ReadyReplicas: 1, UpdatedReplicas: 0, AvailableReplicas: 0, UnavailableReplicas: 0},
	}

	newDeploy := &appsv1.Deployment{
		TypeMeta: metav1.TypeMeta{
			Kind:       "Deployment",
			APIVersion: appsv1.SchemeGroupVersion.String(),
		},
		Status: appsv1.DeploymentStatus{Replicas: 0, ReadyReplicas: 3, UpdatedReplicas: 0, AvailableReplicas: 0, UnavailableReplicas: 0}}
	oldObj, _ := helper.ToUnstructured(oldDeploy)
	newObj, _ := helper.ToUnstructured(newDeploy)

	tests := []struct {
		name                  string
		curObj                *unstructured.Unstructured
		aggregatedStatusItems []workv1alpha2.AggregatedStatusItem
		expectedObj           *unstructured.Unstructured
		luaScript             string
	}{
		{
			name:                  "Test AggregateDeploymentStatus",
			curObj:                oldObj,
			aggregatedStatusItems: aggregatedStatusItems,
			expectedObj:           newObj,
			luaScript: `function AggregateStatus(desiredObj, statusItems) 
										for i = 1, #statusItems do    
											desiredObj.status.readyReplicas = desiredObj.status.readyReplicas + statusItems[i].status.readyReplicas 
										end    
										return desiredObj
									end`,
		},
	}
	vm := New(false, 1)

	for _, tt := range tests {
		actualObj, _ := vm.AggregateStatus(tt.curObj, tt.aggregatedStatusItems, tt.luaScript)
		actualDeploy := appsv1.DeploymentStatus{}
		err := helper.ConvertToTypedObject(actualObj.Object["status"], &actualDeploy)
		if err != nil {
			t.Error(err.Error())
		}
		expectDeploy := appsv1.DeploymentStatus{}
		err = helper.ConvertToTypedObject(tt.expectedObj.Object["status"], &expectDeploy)
		if err != nil {
			t.Error(err.Error())
		}
		if !reflect.DeepEqual(expectDeploy, actualDeploy) {
			t.Errorf("AggregateStatus() got = %v, want %v", actualDeploy, expectDeploy)
		}
	}
}

func TestHealthDeploymentStatus(t *testing.T) {
	var cnt int32 = 2
	newDeploy := &appsv1.Deployment{
		TypeMeta: metav1.TypeMeta{
			Kind:       "Deployment",
			APIVersion: appsv1.SchemeGroupVersion.String(),
		},
		Spec: appsv1.DeploymentSpec{
			Replicas: &cnt,
		},
		ObjectMeta: metav1.ObjectMeta{
			Generation: 1,
		},
		Status: appsv1.DeploymentStatus{ObservedGeneration: 1, Replicas: 2, ReadyReplicas: 2, UpdatedReplicas: 2, AvailableReplicas: 2}}
	newObj, _ := helper.ToUnstructured(newDeploy)

	tests := []struct {
		name        string
		curObj      *unstructured.Unstructured
		expectedObj bool
		luaScript   string
	}{
		{
			name:        "Test HealthDeploymentStatus",
			curObj:      newObj,
			expectedObj: true,
			luaScript: `function InterpretHealth(observedObj)
							return (observedObj.status.updatedReplicas == observedObj.spec.replicas) and (observedObj.metadata.generation == observedObj.status.observedGeneration)
                        end `,
		},
	}
	vm := New(false, 1)

	for _, tt := range tests {
		flag, err := vm.InterpretHealth(tt.curObj, tt.luaScript)

		if err != nil {
			t.Error(err.Error())
		}
		if !reflect.DeepEqual(flag, tt.expectedObj) {
			t.Errorf("AggregateStatus() got = %v, want %v", flag, tt.expectedObj)
		}
	}
}

func TestRetainDeployment(t *testing.T) {
	tests := []struct {
		name        string
		desiredObj  *unstructured.Unstructured
		observedObj *unstructured.Unstructured
		expectError bool
		luaScript   string
	}{
		{
			name: "Test RetainDeployment1",
			desiredObj: &unstructured.Unstructured{
				Object: map[string]interface{}{
					"apiVersion": "apps/v1",
					"kind":       "Deployment",
					"metadata": map[string]interface{}{
						"name": "fake-deployment",
					},
					"spec": map[string]interface{}{
						"replicas": 2,
					},
				},
			},
			observedObj: &unstructured.Unstructured{
				Object: map[string]interface{}{
					"apiVersion": "apps/v1",
					"kind":       "Deployment",
					"metadata": map[string]interface{}{
						"name": "fake-deployment",
					},
					"spec": map[string]interface{}{
						"replicas": int64(2),
					},
				},
			},
			expectError: true,
			luaScript:   "function Retain(desiredObj, observedObj)\n  desiredObj = observedObj\n  return desiredObj\n end",
		},
		{
			name: "Test RetainDeployment2",
			desiredObj: &unstructured.Unstructured{
				Object: map[string]interface{}{
					"apiVersion": "apps/v1",
					"kind":       "Deployment",
					"metadata": map[string]interface{}{
						"name": "fake-deployment",
					},
					"spec": map[string]interface{}{
						"replicas": int64(1),
					},
				},
			},
			observedObj: &unstructured.Unstructured{
				Object: map[string]interface{}{
					"apiVersion": "apps/v1",
					"kind":       "Deployment",
					"metadata": map[string]interface{}{
						"name": "fake-deployment",
					},
					"spec": map[string]interface{}{
						"replicas": int64(1),
					},
				},
			},
			expectError: false,
			luaScript: `function Retain(desiredObj, observedObj) 
							desiredObj = observedObj 
							return desiredObj
						end`,
		},
	}
	vm := New(false, 1)

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			res, err := vm.Retain(tt.desiredObj, tt.observedObj, tt.luaScript)
			if err != nil {
				t.Error(err.Error())
			}
			if !reflect.DeepEqual(res.UnstructuredContent(), tt.observedObj.Object) {
				t.Errorf("Retain() got = %v, want %v", res.UnstructuredContent(), tt.observedObj.Object)
			}
		})
	}
}

func TestStatusReflection(t *testing.T) {
	testMap := map[string]interface{}{"key": "value"}
	wantRawExtension, _ := helper.BuildStatusRawExtension(testMap)
	type args struct {
		object *unstructured.Unstructured
	}
	tests := []struct {
		name      string
		args      args
		want      *runtime.RawExtension
		wantErr   bool
		luaScript string
	}{
		{
			"Test StatusReflection",
			args{
				&unstructured.Unstructured{
					Object: map[string]interface{}{
						"status": testMap,
					},
				},
			},
			wantRawExtension,
			false,
			`function ReflectStatus (observedObj)
						if observedObj.status == nil then	
							return nil   
						end    
					return observedObj.status
					end`,
		},
	}

	vm := New(false, 1)
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := vm.ReflectStatus(tt.args.object, tt.luaScript)
			if (err != nil) != tt.wantErr {
				t.Errorf("reflectWholeStatus() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("reflectWholeStatus() got = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestGetDeployPodDependencies(t *testing.T) {
	newDeploy1 := appsv1.Deployment{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "v1",
			Kind:       "Deployment",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      "test",
			Namespace: "test",
		},
		Spec: appsv1.DeploymentSpec{
			Template: corev1.PodTemplateSpec{
				Spec: corev1.PodSpec{
					ServiceAccountName: "test",
				},
			},
		},
	}
	newDeploy2 := newDeploy1.DeepCopy()
	newDeploy2.Namespace = ""
	newObj1, _ := helper.ToUnstructured(&newDeploy1)
	expect1 := make([]configv1alpha1.DependentObjectReference, 1)
	expect1[0] = configv1alpha1.DependentObjectReference{
		APIVersion: "v1",
		Kind:       "ServiceAccount",
		Namespace:  "test",
		Name:       "test",
	}
	newObj2, _ := helper.ToUnstructured(&newDeploy2)
	expect2 := make([]configv1alpha1.DependentObjectReference, 1)
	expect2[0] = configv1alpha1.DependentObjectReference{
		APIVersion: "v1",
		Kind:       "ServiceAccount",
		Namespace:  "default",
		Name:       "test",
	}
	tests := []struct {
		name      string
		curObj    *unstructured.Unstructured
		luaScript string
		want      []configv1alpha1.DependentObjectReference
	}{
		{
			name:   "Get GetDeployPodDependencies",
			curObj: newObj1,
			luaScript: `function GetDependencies(desiredObj)
							dependentSas = {}
							refs = {}
							if desiredObj.spec.template.spec.serviceAccountName ~= nil and desiredObj.spec.template.spec.serviceAccountName ~= 'default' then
								dependentSas[desiredObj.spec.template.spec.serviceAccountName] = true
							end
						local idx = 1
						for key, value in pairs(dependentSas) do    
							dependObj = {}    
							dependObj.apiVersion = 'v1'   
							dependObj.kind = 'ServiceAccount'    
							dependObj.name = key    
							dependObj.namespace = desiredObj.metadata.namespace    
							refs[idx] = {} 
							refs[idx] = dependObj    
							idx = idx + 1
						end
					return refs
				    end`,
			want: expect1,
		},
		{
			name:   "Test getPodDependencies",
			curObj: newObj1,
			luaScript: `local kube = require("kube")
					function GetDependencies(desiredObj)
						dependencies = kube.getPodDependencies(desiredObj.spec.template, desiredObj.metadata.namespace)
						return dependencies
			            end`,
			want: expect1,
		},
		{
			name:   "Test getPodDependencies when desiredObj's namespace is empty",
			curObj: newObj2,
			luaScript: `local kube = require("kube")
					function GetDependencies(desiredObj)
						dependencies = kube.getPodDependencies(desiredObj.spec.template, desiredObj.metadata.namespace)
						return dependencies
			            end`,
			want: expect2,
		},
	}

	vm := New(false, 1)

	for _, tt := range tests {
		res, err := vm.GetDependencies(tt.curObj, tt.luaScript)
		if err != nil {
			t.Errorf("GetDependencies err %v", err)
		}
		if !reflect.DeepEqual(res, tt.want) {
			t.Errorf("GetDependencies() got = %v, want %v", res, tt.want)
		}
	}
}

func Test_decodeValue(t *testing.T) {
	L := lua.NewState()

	type args struct {
		value interface{}
	}
	tests := []struct {
		name    string
		args    args
		want    lua.LValue
		wantErr bool
	}{
		{
			name: "nil",
			args: args{
				value: nil,
			},
			want: lua.LNil,
		},
		{
			name: "nil pointer",
			args: args{
				value: (*struct{})(nil),
			},
			want: lua.LNil,
		},
		{
			name: "int pointer",
			args: args{
				value: ptr.To[int](1),
			},
			want: lua.LNumber(1),
		},
		{
			name: "int",
			args: args{
				value: 1,
			},
			want: lua.LNumber(1),
		},
		{
			name: "uint",
			args: args{
				value: uint(1),
			},
			want: lua.LNumber(1),
		},
		{
			name: "float",
			args: args{
				value: 1.0,
			},
			want: lua.LNumber(1),
		},
		{
			name: "bool",
			args: args{
				value: true,
			},
			want: lua.LBool(true),
		},
		{
			name: "string",
			args: args{
				value: "foo",
			},
			want: lua.LString("foo"),
		},
		{
			name: "json number",
			args: args{
				value: json.Number("1"),
			},
			want: lua.LString("1"),
		},
		{
			name: "slice",
			args: args{
				value: []string{"foo", "bar"},
			},
			want: func() lua.LValue {
				v := L.CreateTable(2, 0)
				v.Append(lua.LString("foo"))
				v.Append(lua.LString("bar"))
				return v
			}(),
		},
		{
			name: "slice pointer",
			args: args{
				value: &[]string{"foo", "bar"},
			},
			want: func() lua.LValue {
				v := L.CreateTable(2, 0)
				v.Append(lua.LString("foo"))
				v.Append(lua.LString("bar"))
				return v
			}(),
		},
		{
			name: "struct",
			args: args{
				value: struct {
					Foo string
				}{
					Foo: "foo",
				},
			},
			want: func() lua.LValue {
				v := L.CreateTable(0, 1)
				v.RawSetString("Foo", lua.LString("foo"))
				return v
			}(),
		},
		{
			name: "struct pointer",
			args: args{
				value: &struct {
					Foo string
				}{
					Foo: "foo",
				},
			},
			want: func() lua.LValue {
				v := L.CreateTable(0, 1)
				v.RawSetString("Foo", lua.LString("foo"))
				return v
			}(),
		},
		{
			name: "[]interface{}",
			args: args{
				value: []interface{}{1, 2},
			},
			want: func() lua.LValue {
				v := L.CreateTable(2, 0)
				v.Append(lua.LNumber(1))
				v.Append(lua.LNumber(2))
				return v
			}(),
		},
		{
			name: "map[string]interface{}",
			args: args{
				value: map[string]interface{}{
					"foo": "foo1",
				},
			},
			want: func() lua.LValue {
				v := L.CreateTable(0, 1)
				v.RawSetString("foo", lua.LString("foo1"))
				return v
			}(),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := decodeValue(L, tt.args.value)
			if (err != nil) != tt.wantErr {
				t.Errorf("decodeValue() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("decodeValue() got = %v, want %v", got, tt.want)
			}
		})
	}
}
